/**
 * jQuery jPages v0.7
 * Client side pagination with jQuery
 * http://luis-almeida.github.com/jPages
 *
 * Licensed under the MIT license.
 * Copyright 2012 Luís Almeida
 * https://github.com/luis-almeida
 */

;(function($, window, document, undefined) {

	var name = "jPages",
			instance = null,
			defaults = {
				containerID: "",
				first: false,
				previous: "上一页",
				next: "下一页",
				last: false,
				links: "numeric", // blank || title
				startPage: 1,
				perPage: 10,
				midRange: 5,
				startRange: 1,
				endRange: 1,
				keyBrowse: false,
				scrollBrowse: false,
				pause: 0,
				clickStop: false,
				delay: 50,
				direction: "forward", // backwards || auto || random ||
				animation: "", // http://daneden.me/animate/ - any entrance animations
				fallback: 400,
				minHeight: true,
				callback: undefined // function( pages, items ) { }
			};


	function Plugin(element, options) {
		this.options = $.extend({}, defaults, options);

		this._container = $("#" + this.options.containerID);
		if (!this._container.length) return;

		this.jQwindow = $(window);
		this.jQdocument = $(document);

		this._holder = $(element);
		this._nav = {};

		this._first = $(this.options.first);
		this._previous = $(this.options.previous);
		this._next = $(this.options.next);
		this._last = $(this.options.last);

		/* only visible items! */
		this._items = this._container.children(":visible");
		this._itemsShowing = $([]);
		this._itemsHiding = $([]);

		this._numPages = Math.ceil(this._items.length / this.options.perPage);
		this._currentPageNum = this.options.startPage;

		this._clicked = false;
		this._cssAnimSupport = this.getCSSAnimationSupport();

		this.init();
	}

	Plugin.prototype = {

		constructor : Plugin,

		getCSSAnimationSupport : function() {
			var animation = false,
					animationstring = 'animation',
					keyframeprefix = '',
					domPrefixes = 'Webkit Moz O ms Khtml'.split(' '),
					pfx = '',
					elm = this._container.get(0);

			if (elm.style.animationName) animation = true;

			if (animation === false) {
				for (var i = 0; i < domPrefixes.length; i++) {
					if (elm.style[domPrefixes[i] + 'AnimationName'] !== undefined) {
						pfx = domPrefixes[i];
						animationstring = pfx + 'Animation';
						keyframeprefix = '-' + pfx.toLowerCase() + '-';
						animation = true;
						break;
					}
				}
			}

			return animation;
		},

		init : function() {
			this.setStyles();
			this.setNav();
			this.paginate(this._currentPageNum);
			this.setMinHeight();
		},

		setStyles : function() {
			var requiredStyles = "<style>" +
					".jp-invisible { visibility: hidden !important; } " +
					".jp-hidden { display: none !important; }" +
					"</style>";

			$(requiredStyles).appendTo("head");

			if (this._cssAnimSupport && this.options.animation.length)
				this._items.addClass("animated jp-hidden");
			else this._items.hide();

		},

		setNav : function() {
			var navhtml = this.writeNav();

			this._holder.each(this.bind(function(index, element) {
				var holder = $(element);
				holder.html(navhtml);
				this.cacheNavElements(holder, index);
				this.bindNavHandlers(index);
				this.disableNavSelection(element);
			}, this));

			if (this.options.keyBrowse) this.bindNavKeyBrowse();
			if (this.options.scrollBrowse) this.bindNavScrollBrowse();
		},

		writeNav : function() {
			var i = 1, navhtml;
			navhtml = this.writeBtn("first") + this.writeBtn("previous");

			for (; i <= this._numPages; i++) {
				if (i === 1 && this.options.startRange === 0) navhtml += "<li><span>...</span></li>";
				if (i > this.options.startRange && i <= this._numPages - this.options.endRange)
					navhtml += "<li><a href='#' class='jp-hidden'>";
				else
					navhtml += "<li><a>";

				switch (this.options.links) {
					case "numeric":
						navhtml += i;
						break;
					case "blank":
						break;
					case "title":
						var title = this._items.eq(i - 1).attr("data-title");
						navhtml += title !== undefined ? title : "";
						break;
				}

				navhtml += "</a></li>";
				if (i === this.options.startRange || i === this._numPages - this.options.endRange)
					navhtml += "<li><span>...</span></li>";
			}
			navhtml += this.writeBtn("next") + this.writeBtn("last") + "</div>";
			return navhtml;
		},

		writeBtn : function(which) {

			return this.options[which] !== false && !$(this["_" + which]).length ?
			"<li><a class='jp-" + which + "'>" + this.options[which] + "</a></li>" : "";

		},

		cacheNavElements : function(holder, index) {
			this._nav[index] = {};
			this._nav[index].holder = holder;
			this._nav[index].first = this._first.length ? this._first : this._nav[index].holder.find("a.jp-first");
			this._nav[index].previous = this._previous.length ? this._previous : this._nav[index].holder.find("a.jp-previous");
			this._nav[index].next = this._next.length ? this._next : this._nav[index].holder.find("a.jp-next");
			this._nav[index].last = this._last.length ? this._last : this._nav[index].holder.find("a.jp-last");
			this._nav[index].fstBreak = this._nav[index].holder.find("span:first");
			this._nav[index].lstBreak = this._nav[index].holder.find("span:last");
			this._nav[index].pages = this._nav[index].holder.find("a").not(".jp-first, .jp-previous, .jp-next, .jp-last");
			this._nav[index].permPages =
					this._nav[index].pages.slice(0, this.options.startRange)
							.add(this._nav[index].pages.slice(this._numPages - this.options.endRange, this._numPages));
			this._nav[index].pagesShowing = $([]);
			this._nav[index].currentPage = $([]);
		},

		bindNavHandlers : function(index) {
			var nav = this._nav[index];

			// default nav
			nav.holder.bind("click.jPages", this.bind(function(evt) {
				var newPage = this.getNewPage(nav, $(evt.target));
				if (this.validNewPage(newPage)) {
					this._clicked = true;
					this.paginate(newPage);
				}
				evt.preventDefault();
			}, this));

			// custom first
			if (this._first.length) {
				this._first.bind("click.jPages", this.bind(function() {
					if (this.validNewPage(1)) {
						this._clicked = true;
						this.paginate(1);
					}
				}, this));
			}

			// custom previous
			if (this._previous.length) {
				this._previous.bind("click.jPages", this.bind(function() {
					var newPage = this._currentPageNum - 1;
					if (this.validNewPage(newPage)) {
						this._clicked = true;
						this.paginate(newPage);
					}
				}, this));
			}

			// custom next
			if (this._next.length) {
				this._next.bind("click.jPages", this.bind(function() {
					var newPage = this._currentPageNum + 1;
					if (this.validNewPage(newPage)) {
						this._clicked = true;
						this.paginate(newPage);
					}
				}, this));
			}

			// custom last
			if (this._last.length) {
				this._last.bind("click.jPages", this.bind(function() {
					if (this.validNewPage(this._numPages)) {
						this._clicked = true;
						this.paginate(this._numPages);
					}
				}, this));
			}

		},

		disableNavSelection : function(element) {
			if (typeof element.onselectstart != "undefined")
				element.onselectstart = function() {
					return false;
				};
			else if (typeof element.style.MozUserSelect != "undefined")
				element.style.MozUserSelect = "none";
			else
				element.onmousedown = function() {
					return false;
				};
		},

		bindNavKeyBrowse : function() {
			this.jQdocument.bind("keydown.jPages", this.bind(function(evt) {
				var target = evt.target.nodeName.toLowerCase();
				if (this.elemScrolledIntoView() && target !== "input" && target != "textarea") {
					var newPage = this._currentPageNum;

					if (evt.which == 37) newPage = this._currentPageNum - 1;
					if (evt.which == 39) newPage = this._currentPageNum + 1;

					if (this.validNewPage(newPage)) {
						this._clicked = true;
						this.paginate(newPage);
					}
				}
			}, this));
		},

		elemScrolledIntoView : function() {
			var docViewTop, docViewBottom, elemTop, elemBottom;
			docViewTop = this.jQwindow.scrollTop();
			docViewBottom = docViewTop + this.jQwindow.height();
			elemTop = this._container.offset().top;
			elemBottom = elemTop + this._container.height();
			return ((elemBottom >= docViewTop) && (elemTop <= docViewBottom));

			// comment above and uncomment below if you want keyBrowse to happen
			// only when container is completely visible in the page
			/*return ((elemBottom >= docViewTop) && (elemTop <= docViewBottom) &&
			 (elemBottom <= docViewBottom) &&  (elemTop >= docViewTop) );*/
		},

		bindNavScrollBrowse : function() {
			this._container.bind("mousewheel.jPages DOMMouseScroll.jPages", this.bind(function(evt) {
				var newPage = (evt.originalEvent.wheelDelta || -evt.originalEvent.detail) > 0 ?
						(this._currentPageNum - 1) : (this._currentPageNum + 1);
				if (this.validNewPage(newPage)) {
					this._clicked = true;
					this.paginate(newPage);
				}
				evt.preventDefault();
				return false;
			}, this));
		},

		getNewPage : function(nav, target) {
			if (target.is(nav.currentPage)) return this._currentPageNum;
			if (target.is(nav.pages)) return nav.pages.index(target) + 1;
			if (target.is(nav.first)) return 1;
			if (target.is(nav.last)) return this._numPages;
			if (target.is(nav.previous)) return nav.pages.index(nav.currentPage);
			if (target.is(nav.next)) return nav.pages.index(nav.currentPage) + 2;
		},

		validNewPage : function(newPage) {
			return newPage !== this._currentPageNum && newPage > 0 && newPage <= this._numPages;
		},

		paginate : function(page) {
			var itemRange, pageInterval;
			itemRange = this.updateItems(page);
			pageInterval = this.updatePages(page);
			this._currentPageNum = page;
			if ($.isFunction(this.options.callback))
				this.callback(page, itemRange, pageInterval);

			this.updatePause();
		},

		updateItems : function(page) {
			var range = this.getItemRange(page);
			this._itemsHiding = this._itemsShowing;
			this._itemsShowing = this._items.slice(range.start, range.end);
			if (this._cssAnimSupport && this.options.animation.length) this.cssAnimations(page);
			else this.jQAnimations(page);
			return range;
		},

		getItemRange : function(page) {
			var range = {};
			range.start = (page - 1) * this.options.perPage;
			range.end = range.start + this.options.perPage;
			if (range.end > this._items.length) range.end = this._items.length;
			return range;
		},

		cssAnimations : function(page) {
			clearInterval(this._delay);

			this._itemsHiding
					.removeClass(this.options.animation + " jp-invisible")
					.addClass("jp-hidden");

			this._itemsShowing
					.removeClass("jp-hidden")
					.addClass("jp-invisible");

			this._itemsOriented = this.getDirectedItems(page);
			this._index = 0;

			this._delay = setInterval(this.bind(function() {
				if (this._index === this._itemsOriented.length) clearInterval(this._delay);
				else {
					this._itemsOriented
							.eq(this._index)
							.removeClass("jp-invisible")
							.addClass(this.options.animation);
				}
				this._index = this._index + 1;
			}, this), this.options.delay);
		},

		jQAnimations : function(page) {
			clearInterval(this._delay);
			this._itemsHiding.addClass("jp-hidden");
			this._itemsShowing.fadeTo(0, 0).removeClass("jp-hidden");
			this._itemsOriented = this.getDirectedItems(page);
			this._index = 0;
			this._delay = setInterval(this.bind(function() {
				if (this._index === this._itemsOriented.length) clearInterval(this._delay);
				else {
					this._itemsOriented
							.eq(this._index)
							.fadeTo(this.options.fallback, 1);
				}
				this._index = this._index + 1;
			}, this), this.options.delay);
		},

		getDirectedItems : function(page) {
			var itemsToShow;

			switch (this.options.direction) {
				case "backwards":
					itemsToShow = $(this._itemsShowing.get().reverse());
					break;
				case "random":
					itemsToShow = $(this._itemsShowing.get().sort(function() {
						return (Math.round(Math.random()) - 0.5);
					}));
					break;
				case "auto":
					itemsToShow = page >= this._currentPageNum ?
							this._itemsShowing : $(this._itemsShowing.get().reverse());
					break;
				default:
					itemsToShow = this._itemsShowing;
			}

			return itemsToShow;
		},

		updatePages : function(page) {
			var interval, index, nav;
			interval = this.getInterval(page);
			for (index in this._nav) {
				if (this._nav.hasOwnProperty(index)) {
					nav = this._nav[index];
					this.updateBtns(nav, page);
					this.updateCurrentPage(nav, page);
					this.updatePagesShowing(nav, interval);
					this.updateBreaks(nav, interval);
				}
			}
			return interval;
		},

		getInterval : function(page) {
			var neHalf, upperLimit, start, end;
			neHalf = Math.ceil(this.options.midRange / 2);
			upperLimit = this._numPages - this.options.midRange;
			start = page > neHalf ? Math.max(Math.min(page - neHalf, upperLimit), 0) : 0;
			end = page > neHalf ?
					Math.min(page + neHalf - (this.options.midRange % 2 > 0 ? 1 : 0), this._numPages) :
					Math.min(this.options.midRange, this._numPages);
			return {start: start,end: end};
		},

		updateBtns : function(nav, page) {
			if (page === 1) {
				nav.first.addClass("jp-disabled");
				nav.previous.addClass("jp-disabled");
			}
			if (page === this._numPages) {
				nav.next.addClass("jp-disabled");
				nav.last.addClass("jp-disabled");
			}
			if (this._currentPageNum === 1 && page > 1) {
				nav.first.removeClass("jp-disabled");
				nav.previous.removeClass("jp-disabled");
			}
			if (this._currentPageNum === this._numPages && page < this._numPages) {
				nav.next.removeClass("jp-disabled");
				nav.last.removeClass("jp-disabled");
			}
		},

		updateCurrentPage : function(nav, page) {
			nav.currentPage.removeClass("jp-current");
			nav.currentPage = nav.pages.eq(page - 1).addClass("jp-current");
		},

		updatePagesShowing : function(nav, interval) {
			var newRange = nav.pages.slice(interval.start, interval.end).not(nav.permPages);
			nav.pagesShowing.not(newRange).addClass("jp-hidden");
			newRange.not(nav.pagesShowing).removeClass("jp-hidden");
			nav.pagesShowing = newRange;
		},

		updateBreaks : function(nav, interval) {
			if (
					interval.start > this.options.startRange ||
					(this.options.startRange === 0 && interval.start > 0)
			) nav.fstBreak.removeClass("jp-hidden");
			else nav.fstBreak.addClass("jp-hidden");

			if (interval.end < this._numPages - this.options.endRange) nav.lstBreak.removeClass("jp-hidden");
			else nav.lstBreak.addClass("jp-hidden");
		},

		callback : function(page, itemRange, pageInterval) {
			var pages = {
						current: page,
						interval: pageInterval,
						count: this._numPages
					},
					items = {
						showing: this._itemsShowing,
						oncoming: this._items.slice(itemRange.start + this.options.perPage, itemRange.end + this.options.perPage),
						range: itemRange,
						count: this._items.length
					};

			pages.interval.start = pages.interval.start + 1;
			items.range.start = items.range.start + 1;
			this.options.callback(pages, items);
		},

		updatePause : function() {
			if (this.options.pause && this._numPages > 1) {
				clearTimeout(this._pause);
				if (this.options.clickStop && this._clicked) return;
				else {
					this._pause = setTimeout(this.bind(function() {
						this.paginate(this._currentPageNum !== this._numPages ? this._currentPageNum + 1 : 1);
					}, this), this.options.pause);
				}
			}
		},

		setMinHeight : function() {
			if (this.options.minHeight && !this._container.is("table, tbody")) {
				setTimeout(this.bind(function() {
					this._container.css({ "min-height": this._container.css("height") });
				}, this), 1000);
			}
		},

		bind : function(fn, me) {
			return function() {
				return fn.apply(me, arguments);
			};
		},

		destroy : function() {
			this.jQdocument.unbind("keydown.jPages");
			this._container.unbind("mousewheel.jPages DOMMouseScroll.jPages");

			if (this.options.minHeight) this._container.css("min-height", "");
			if (this._cssAnimSupport && this.options.animation.length)
				this._items.removeClass("animated jp-hidden jp-invisible " + this.options.animation);
			else this._items.removeClass("jp-hidden").fadeTo(0, 1);
			this._holder.unbind("click.jPages").empty();
		}

	};

	$.fn[name] = function(arg) {
		var type = $.type(arg);

		if (type === "object") {
			if (this.length && !$.data(this, name)) {
				instance = new Plugin(this, arg);
				this.each(function() {
					$.data(this, name, instance);
				});
			}
			return this;
		}

		if (type === "string" && arg === "destroy") {
			instance.destroy();
			this.each(function() {
				$.removeData(this, name);
			});
			return this;
		}

		if (type === 'number' && arg % 1 === 0) {
			if (instance.validNewPage(arg)) instance.paginate(arg);
			return this;
		}

		return this;
	};

})(jQuery, window, document);
